package com.dtx.aop;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.dtx.anno.ApiLog;
import com.dtx.entity.SysLog;
import com.dtx.msg.ResultMsg;
import com.dtx.service.ISysLogService;
import com.dtx.state.Global;
import com.dtx.utils.ByteUtils;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

@Log4j2
@Aspect
@Component
public class SysLogAnnoAspect {

    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

    @Autowired
    private Executor customThreadPoolTaskExecutor;

    @Autowired
    private ISysLogService sysLogService;

    /**
     * 日志切点
     */
    @Pointcut("@annotation(com.dtx.anno.ApiLog)")
    public void SysLogAnnoAspect() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before(value = "SysLogAnnoAspect()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        SysLog sysLog = new SysLog();
        HttpServletRequest request = ((ServletRequestAttributes) Objects
                .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        //获取操作
        ApiLog operation = method.getAnnotation(ApiLog.class);
        if (operation != null) {
            String value = operation.value();
            sysLog.setDescribe(value);//保存获取的操作
        }
        // 创建人信息请根据实际项目获取方式获取
        sysLog.setCreateUserCode("");
        sysLog.setCreateUserName("");
        sysLog.setStartTime(new Date());
        sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
        sysLog.setRequestParams(formatParams(request.getParameterMap()));
        sysLog.setRequestMethod(request.getMethod());
        sysLog.setRequestIp(ServletUtil.getClientIP(request));
        sysLog.setServerAddress(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort());
        String userAgentStr = request.getHeader("User-Agent");
        sysLog.setUserAgent(userAgentStr);
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
        sysLog.setDeviceName(userAgent.getOs().getName());
        sysLog.setBrowserName(userAgent.getBrowser().getName());

        sysLogThreadLocal.set(sysLog);

        log.info("开始计时: {}  URI: {}  IP: {}", sysLog.getStartTime(), sysLog.getRequestUri(), sysLog.getRequestIp());
    }

    /**
     * 返回通知
     *
     * @param ret
     */
    @AfterReturning(pointcut = "SysLogAnnoAspect()", returning = "ret")
    public void doAfterReturning(Object ret) {

        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setLogType(Global.LOG_INGO.getDescription());
        sysLog.setEndTime(new Date());
        sysLog.setExecuteTime(ChronoUnit.MILLIS.between(sysLog.getStartTime().toInstant(), sysLog.getEndTime().toInstant()));
        ResultMsg<?> codeMsg = Convert.convert(ResultMsg.class, ret);
        if (codeMsg.getCode()!=null) {
            if (codeMsg.getCode().equals(Integer.valueOf(Global.TRUE.getDescription()))) {
                sysLog.setIsException(Global.NO.getDescription());
            } else {
                sysLog.setIsException(Global.YES.getDescription());
                sysLog.setExceptionInfo(codeMsg.getMsg());
            }
        }else {
            sysLog.setIsException(Global.NO.getDescription());
        }
        customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
        sysLogThreadLocal.remove();

        Runtime runtime = Runtime.getRuntime();
        log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
                sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
                ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
    }

    /**
     * 异常通知
     *
     * @param e
     */
    @AfterThrowing(pointcut = "SysLogAnnoAspect()", throwing = "e")
    public void doAfterThrowable(Throwable e) {
        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setLogType(Global.LOG_ERROR.getDescription());
        sysLog.setEndTime(new Date());
        sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MINUTES.between(sysLog.getStartTime().toInstant(), sysLog.getEndTime().toInstant())));
        sysLog.setIsException(Global.YES.getDescription());
        sysLog.setExceptionInfo(e.getMessage());
        customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
        sysLogThreadLocal.remove();

        Runtime runtime = Runtime.getRuntime();
        log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
                sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
                ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
    }

    /**
     * 格式化参数
     *
     * @param parameterMap
     * @return
     */
    private String formatParams(Map<String, String[]> parameterMap) {
        if (parameterMap == null) {
            return null;
        }
        StringBuilder params = new StringBuilder();
        for (Map.Entry<String, String[]> param : (parameterMap).entrySet()) {
            if (params.length() != 0) {
                params.append("&");
            }
            params.append(param.getKey() + "=");
            if (StrUtil.endWithIgnoreCase(param.getKey(), "password")) {
                params.append("*");
            } else if (param.getValue() != null) {
                params.append(ArrayUtil.join(param.getValue(), ","));
            }
        }
        return params.toString();
    }

    /**
     * 保存日志线程
     *
     * @author CL
     *
     */
    private static class SaveLogThread extends Thread {
        private SysLog sysLog;
        private ISysLogService sysLogService;

        public SaveLogThread(SysLog sysLog, ISysLogService sysLogService) {
            this.sysLog = sysLog;
            this.sysLogService = sysLogService;
        }

        @Override
        public void run() {
            sysLog.setCreateDate(new Date());
            sysLogService.save(sysLog);
        }
    }
}
